﻿// Load.cs
//
// Copyright 2009-2011 by Burt Harris
// Licensed under the Microsoft Public License (Ms-PL)
// See: http://www.codeplex.com/yaml/license
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.Serialization;

namespace YaTools.Yaml
{
    public sealed class Load
    {
        /// <summary>
        /// Current mapping of tag-prefixes to tag-handles
        /// </summary>
        private readonly List<KeyValuePair<string, string>> _prefixHandlePairs =
            new List<KeyValuePair<string, string>>
                {
                    new KeyValuePair
                        <string, string>(
                        Yaml.Tag.DefaultGlobalPrefix,
                        Yaml.Tag.GlobalHandle)
                };

        // Cached mappings of tags shorthand to Tag.   Cleared at the end of each document.
        private readonly Dictionary<string, string> _tagCache
            = new Dictionary<string, string>();

        private readonly Stack<Waypoint> _waypointStack = new Stack<Waypoint>();
        private Waypoint _active;
        private int _anchorCount;
        private AnchorObjectManager _anchorObjectManager;
        private readonly StreamingContext _streamingContext;
        private SurrogateSelector _surrogateSelector;

        public Load(StreamingContextStates states = StreamingContextStates.All)
        {
            Contract.Ensures(_prefixHandlePairs != null);
            Contract.Ensures(_waypointStack != null);
            Contract.Assume(this.Count == 0);

            _streamingContext = new StreamingContext(states, this);
            _active = new Waypoint {Compose = DummyCompose.Instance};
        }

        public Load(Compose sink)
            : this()
        {
            Contract.Ensures(this.Depth == 0);
            Contract.Assume(this.Count == 0);
            _active = new Waypoint {Compose = sink};
            ResetProperties();
            Contract.Assume(this.Depth == 0);
        }

        // ReSharper disable UnusedMember.Local
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(this._waypointStack != null);
            Contract.Invariant(this._prefixHandlePairs != null);
            Contract.Invariant(this._tagCache != null);
            Contract.Invariant(this.Depth >= 0);
            Contract.Invariant(this.NestingLevel >= 0);
            Contract.Invariant(this._active.Compose != null);
        }

        // ReSharper restore UnusedMember.Local

        public int Depth
        {
            [Pure]
            get { return _waypointStack.Count; }
        }

        public string Anchor
        {
            get { return _active.Anchor; }
            set { _active.Anchor = value; }
        }

        public Tag Tag
        {
            get { return _active.Tag; }
            set { _active.Tag = value; }
        }

        public object Object
        {
            get { return _active.Object; }
            set { _active.Object = value; }
        }

        public object Key
        {
            get { return _active.Key; }
            set { _active.Key = value; }
        }

        public Type ExpectedType { get; set; }

        public int NestingLevel
        {
            get { return _active.Nesting; }
        }

        public int Count
        {
            get { return _active.Count; }
        }

        public bool IsKey
        {
            get { return _active.IsKey; }
        }

        public bool PreviousWasKey
        {
            get { return _active.PreviousWasKey; }
        }

        public Kind Kind
        {
            get { return _active.Kind; }
            private set { _active.Kind = value; }
        }

        internal AnchorObjectManager AnchorObjectManager
        {
            get
            {
                return _anchorObjectManager ?? (_anchorObjectManager
                                                = new AnchorObjectManager(SurrogateSelector, _streamingContext));
            }
        }

        private ISurrogateSelector SurrogateSelector
        {
            get { return _surrogateSelector ?? (_surrogateSelector = new SurrogateSelector()); }
        }

        private void PushActive()
        {
            Contract.Ensures(this.Depth == 1 + Contract.OldValue<int>(this.Depth));
            _waypointStack.Push(_active);
        }

        public void Initialize(Compose sink)
        {
            if (_active.Compose != DummyCompose.Instance)
                throw new InvalidOperationException(
                    "Only call Initialize once");

            _active = new Waypoint {Compose = sink};
            ResetProperties();
        }

        private void ResetProperties()
        {
            _active.Tag = Tag.NonSpecific;
            _active.Anchor = null;
            ExpectedType = null;
            if (_active.Kind == Kind.Mapping && !_active.IsKey && !_active.PreviousWasKey)
                _active.IsKey = true;
        }

        public bool BeginMap(object value)
        {
            // Contract.Requires(value != null);

            if (_active.Tag == Tag.NonSpecific) _active.Tag = Tag.Map;
            if (_active.Compose.BeginObject(this, value))
            {
                PushActive();
                Kind = Kind.Mapping;
                _active.IsKey = true;
                _active.PreviousWasKey = false;
                _active.Count = 0;
                _active.Nesting++;
                _active.Compose = _active.Compose.BeginMappingNode(this);
                ResetProperties();
                return true;
            }
            //_waypointStack.Push(new Waypoint());
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            ResetProperties();
            return false;
        }

        public bool BeginSeq(object value)
        {
            if (_active.Tag == Tag.NonSpecific) _active.Tag = Tag.Sequence;
            if (_active.Compose.BeginObject(this, value))
            {
                PushActive();
                Kind = Kind.Sequence;
                _active.Count = 0;
                _active.IsKey = false;
                _active.PreviousWasKey = false;
                _active.Nesting++;
                _active.Compose = _active.Compose.BeginSequenceNode(this);
                ResetProperties();
                return true;
            }
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            //_waypointStack.Push(new Waypoint());
            ResetProperties();
            return false;
        }

        public bool BeginDocument()
        {
            _active.Compose.BeginDocument(this);
            PushActive(); // Does this belong?
            // Contract.Assert(this.Depth == 1);
            return true;
        }

        public void EndDocument()
        {
            _active.Compose.ComposeDocument(this);
        }

        public void EndMappingNode()
        {
            Contract.Assume(this.Depth > 0);
            Waypoint saved = _waypointStack.Pop();
            if (saved.Compose != null)
            {
                _active.Anchor = saved.Anchor;
                _active.Tag = saved.Tag;
                object result = _active.Compose.ComposeMappingNode(this);
                _active = saved;
                _active.Count++;
                _active.Compose.EndObject(this, result);
                _active.PreviousWasKey = _active.IsKey;
            }
            ResetProperties();
        }

        public void EndSequenceNode()
        {
            Contract.Assume(this.Depth > 0);
            Waypoint saved = _waypointStack.Pop();
            if (saved.Compose != null)
            {
                _active.Anchor = saved.Anchor;
                _active.Tag = saved.Tag;
                object result = _active.Compose.ComposeSequenceNode(this);
                _active = saved;
                _active.Count++;
                _active.Compose.EndObject(this, result);
                _active.PreviousWasKey = _active.IsKey;
            }
            ResetProperties();
        }


        public void AddScalar(string value)
        {
            Contract.Requires(value != null);

            if (_active.Compose.BeginObject(this, value))
            {
                object result = _active.Compose.AddScalarNode(this, value);
                _active.Compose.EndObject(this, result);
            }
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            ResetProperties();
        }

        public void AddScalar(Tag tag, string value)
        {
            Contract.Requires(value != null);

            Tag = tag;
            if (_active.Compose.BeginObject(this, value))
            {
                object result = _active.Compose.AddScalarNode(this, value);
                _active.Compose.EndObject(this, result);
            }
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            ResetProperties();
        }

        public void AddScalar(Tag tag, object actualValue, string value)
        {
            Contract.Requires(value != null);

            Tag = tag;
            if (_active.Compose.BeginObject(this, actualValue))
            {
                object result = _active.Compose.AddScalarNode(this, value);
                _active.Compose.EndObject(this, result);
            }
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            ResetProperties();
        }


        public void AddAlias(string anchorName)
        {
            _active.Compose.BeginObject(this, null);
            object result = _active.Compose.AddAliasNode(this, anchorName);
            _active.Compose.EndObject(this, result);
            _active.Count++;
            _active.PreviousWasKey = _active.IsKey;
            _active.IsKey = false;
            ResetProperties();
        }

        public string HandleForTag(string tag)
        {
            Contract.Requires(tag != null);

            if (tag.StartsWith("!")) return tag;

            string handle;
            if (!_tagCache.TryGetValue(tag, out handle))
            {
                handle = GenerateHandle(tag);
                _tagCache[tag] = handle;
            }
            return handle;
        }

        private string GenerateHandle(string tag)
        {
            Contract.Requires(tag != null);
            string prefix = string.Empty;
            string shortcut = null;

            foreach (var pair in _prefixHandlePairs)
            {
                Contract.Assume(pair.Key != null);
                if (pair.Key.Length > prefix.Length && tag.StartsWith(pair.Key))
                {
                    prefix = pair.Key;
                    shortcut = pair.Value;
                }
            }
            if (prefix.Length > 0)
            {
                Contract.Assume(tag.Length >= prefix.Length);
                return shortcut + tag.Remove(0, prefix.Length);
            }
            return string.Format("!<{0}>", tag);
        }

        internal string GenerateAnchor(object component)
        {
            Contract.Requires(component != null);

            return TypeDescriptor.GetFullComponentName(component) ??
                   (_anchorCount++).ToString();
        }

        #region Nested type: DummySink

        private class DummyCompose : Compose
        {
            public static readonly DummyCompose Instance
                = new DummyCompose();

            private DummyCompose()
            {
            }

            private static Exception NotInitialized()
            {
                return new InvalidOperationException(
                    "ParseContext Sink not initialized");
            }

            public override Compose BeginMappingNode(Load load)
            {
                throw NotInitialized();
            }

            public override object ComposeMappingNode(Load load)
            {
                throw NotInitialized();
            }

            public override Compose BeginSequenceNode(Load load)
            {
                throw NotInitialized();
            }

            public override object ComposeSequenceNode(Load load)
            {
                throw NotInitialized();
            }

            public override object AddNullNode(Load load)
            {
                throw NotInitialized();
            }

            public override object AddScalarNode(Load load, string value)
            {
                throw NotInitialized();
            }

            public override object AddAliasNode(Load load, string anchor)
            {
                throw NotInitialized();
            }

            public override bool BeginObject(Load load, object value)
            {
                throw NotInitialized();
            }

            public override void EndObject(Load load, object value)
            {
                throw NotInitialized();
            }

            public void EndDocument()
            {
                throw NotInitialized();
            }


            public override Compose BeginDocument(Load load)
            {
                throw new NotImplementedException();
            }

            public override object ComposeDocument(Load load)
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region Nested type: Waypoint

        private struct Waypoint
        {
            public string Anchor;
            public object Object;
            public Compose Compose;
            public int Count;
            public bool IsKey;
            public object Key;
            public Kind Kind;
            public int Nesting;
            public bool PreviousWasKey;
            public Tag Tag;
        }

        #endregion
    }


    /// <summary>
    /// Abstract sink of YAML deserialization events.
    /// </summary>
    [ContractClass(typeof (AbstractContracts.ComposeContract))]
    public abstract class Compose
    {
        /// <summary>Starts a document </summary>
        public abstract Compose BeginDocument(Load load);
        public abstract object ComposeDocument(Load load);
        
        /// <summary>Creates a nested mapping node</summary>
        public abstract Compose BeginMappingNode(Load load);

        /// <summary>Ends a nested mapping node</summary>
        public abstract object ComposeMappingNode(Load load);

        /// <summary>Creates a nested sequence node</summary>
        public abstract Compose BeginSequenceNode(Load load);
        public abstract object ComposeSequenceNode(Load load);

        public abstract object AddNullNode(Load load);

        public abstract object AddScalarNode(Load load, string value);

        public abstract object AddAliasNode(Load load, string anchor);

        public abstract bool BeginObject(Load load, object value);

        public abstract void EndObject(Load load, object value);
    }

    namespace AbstractContracts
    {
        [ContractClassFor(typeof (Compose))]
        internal abstract class ComposeContract : Compose
        {
            public override Compose BeginMappingNode(Load load)
            {
                Contract.Requires(load != null);
                Contract.Ensures(Contract.Result<Compose>() != null);
                return null;
            }

            public override object ComposeMappingNode(Load load)
            {
                Contract.Requires(load != null);
                return null;
            }

            public override Compose BeginSequenceNode(Load load)
            {
                Contract.Requires(load != null);
                Contract.Ensures(Contract.Result<Compose>() != null);
                return null;
            }

            public override object ComposeSequenceNode(Load load)
            {
                Contract.Requires(load != null);
                return null;
            }

            public override object AddNullNode(Load load)
            {
                Contract.Requires(load != null);
                return null;
            }

            public override object AddScalarNode(Load load, string value)
            {
                Contract.Requires(load != null);
                Contract.Requires(value != null);
                return null;
            }

            public override object AddAliasNode(Load load, string anchor)
            {
                Contract.Requires(load != null);
                return null;
            }

            public override bool BeginObject(Load load, object value)
            {
                Contract.Requires(load != null);
                return true;
            }

            public override void EndObject(Load load, object value)
            {
                Contract.Requires(load != null);
            }
        }
    }
}